
//////////////////////////////////////////////////////////////////////
//
//	Crytek CryENGINE Source code
//	
//	File:Timer.cpp
//  Description: Implementation of the timer class: timing functions
//
//	History:
//	-Jan 31,2001:Created by Marco Corbetta
//
//////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "Timer.h"
#include <time.h>
#include <ISystem.h>
#include <IConsole.h>
#include <ILog.h>
#include <ISerialize.h>
/////////////////////////////////////////////////////

#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#include "Mmsystem.h"
#endif

//this should not be included here
#include <IRenderer.h>									// needed for m_pSystem->GetIRenderer()->EF_Query(EFQ_RecurseLevel)

//#define PROFILING 1
#ifdef PROFILING
static int64 g_lCurrentTime = 0;
#endif

//! Profile smoothing time in seconds (original default was .8 / log(10) ~= .35 s)
static const float fDEFAULT_PROFILE_SMOOTHING = 1.0f;
static const float fMAX_BLEND_FRAME_TIME = 1.0f;


#define DEFAULT_FRAME_SMOOTHING 1

/////////////////////////////////////////////////////
CTimer::CTimer() 
{
	m_lBaseTime=0;
	m_lLastTime=0;
	m_lCurrentTime=0;
	m_fFrameTime=0;
	m_fRealFrameTime=0;
	m_lOffsetTime=0;

	m_fixed_time_step = 0;
	m_max_time_step = 0.25f;
  m_time_scale = 1.0f;

	// note: frame numbers (old version - commented out) are not used but is based on time
	m_TimeSmoothing = DEFAULT_FRAME_SMOOTHING; 
	
	m_fAverageFrameTime = 1.0f/30.0f;	//lets hope the game will run with 30 frames on average
	for (int i=0; i<MAX_FRAME_AVERAGE; i++) 
		m_arrFrameTimes[i] = m_fAverageFrameTime; 
	
	m_profile_smooth_time = fDEFAULT_PROFILE_SMOOTHING;
	m_profile_weighting = 1;
	m_fAvgFrameTime = 0;

	m_bEnabled = true;
	m_TimeDebug = 0;
	m_lGameTimerPausedTime = 0;
	m_bGameTimerPaused = false;
	m_lForcedGameTime = -1;

	m_nFrameCounter=0;

	LARGE_INTEGER TTicksPerSec;

	if (QueryPerformanceFrequency(&TTicksPerSec))
	{ 
		// performance counter is available, use it instead of multimedia timer
		m_lTicksPerSec = TTicksPerSec.QuadPart;
		ResetTimer();
	}
	else
	{
		assert(0);
	}

	m_fSecsPerTick = 1.0 / m_lTicksPerSec;
}

/////////////////////////////////////////////////////
bool CTimer::Init(ISystem *pSystem)
{
	m_pSystem=pSystem;

	// MartinM: renamed cvars for consistency
	// if game code was accessing them by name there was something wrong anyway

	REGISTER_CVAR2("t_Smoothing",&m_TimeSmoothing,DEFAULT_FRAME_SMOOTHING,0,
		"time smoothing\n"
		"0=off, 1=on");

	REGISTER_CVAR2("t_FixedStep",&m_fixed_time_step,0,0,
		"Game updated with this fixed frame time\n"
		"0=off, number specifies the frame time in seconds\n"
		"e.g. 0.033333(30 fps), 0.1(10 fps), 0.01(100 fps)" );

	REGISTER_CVAR2("t_MaxStep",&m_max_time_step,0.25f,0,
		"Game systems clamped to this frame time" );

	// todo: reconsider exposing that as cvar (negative time, same value is used by Trackview, better would be another value multipled with the internal one)
  REGISTER_CVAR2("t_Scale",&m_time_scale,1.0f,0,
		"Game time scaled by this - for variable slow motion" );

	REGISTER_CVAR2("t_Debug",&m_TimeDebug,0,0, "Timer debug" );

	// -----------------

	REGISTER_CVAR2("profile_smooth",&m_profile_smooth_time, fDEFAULT_PROFILE_SMOOTHING, 0,
		"Profiler exponential smoothing interval (seconds)" );

	REGISTER_CVAR2("profile_weighting",&m_profile_weighting,1,0,
		"Profiler smoothing mode: 0 = legacy, 1 = average, 2 = peak weighted, 3 = peak hold" );

	return true;
}

/////////////////////////////////////////////////////
SPU_INDIRECT(UpdateParticles(M),ComputeVertices(M))
float CTimer::GetFrameTime(ETimer which) const
{
	if(!m_bEnabled) 
		return 0.0f; 
	if (which != ETIMER_GAME || m_bGameTimerPaused == false)
		return m_fFrameTime;
	return 0.0f;
}

/////////////////////////////////////////////////////
SPU_INDIRECT(CommandBufferExecute(M))
float CTimer::GetCurrTime(ETimer which) const 
{
	return m_CurrTime[(int)which].GetSeconds();
}

/////////////////////////////////////////////////////
float CTimer::GetRealFrameTime() const 
{ 
	if(!m_bEnabled) 
		return 0.0f; 

	return m_fRealFrameTime;   
} 

/////////////////////////////////////////////////////
float CTimer::GetTimeScale() const 
{ 
	return m_time_scale;   
} 

/////////////////////////////////////////////////////
void CTimer::SetTimeScale(float scale)
{ 
	m_time_scale = scale;
} 

/////////////////////////////////////////////////////
float CTimer::GetAsyncCurTime()  
{ 
	int64 llNow = CryGetTicks() - m_lBaseTime;
	return TicksToSeconds(llNow);
}

/////////////////////////////////////////////////////
float CTimer::GetFrameRate()
{
	// Use real frame time.
	if (m_fRealFrameTime != 0.f)
		return 1.f / m_fRealFrameTime;
	return 0.f;
}

void CTimer::UpdateBlending()
{
	// Accumulate smoothing time up to specified max.
	float fSmoothTime = min(m_profile_smooth_time, m_CurrTime[ETIMER_UI].GetSeconds());
	float fFrameTime = min(m_fRealFrameTime, fMAX_BLEND_FRAME_TIME);
	if (fSmoothTime == 0.f)
	{
		m_fAvgFrameTime = fFrameTime;
		m_fProfileBlend = 1.f;
		return;
	}

	// Blend current frame into blended previous.
	m_fProfileBlend = 1.f - expf(-fFrameTime / fSmoothTime);
	if (m_profile_weighting >= 3)
	{
		// Decay avg frame time, set as new peak.
		m_fAvgFrameTime *= 1.f - m_fProfileBlend;
		if (fFrameTime > m_fAvgFrameTime)
		{
			m_fAvgFrameTime = fFrameTime;
			m_fProfileBlend = 1.f;
		}
		else
			m_fProfileBlend = 0.f;
	}
	else
	{
		// Track average frame time.
		m_fAvgFrameTime += m_fProfileBlend * (fFrameTime - m_fAvgFrameTime);
		if (m_profile_weighting == 1)
			// Weight all frames evenly.
			m_fProfileBlend = 1.f - expf(-m_fAvgFrameTime / fSmoothTime);
	}
}

float CTimer::GetProfileFrameBlending( float* pfBlendTime, int* piBlendMode )
{
	if (piBlendMode)
		*piBlendMode = m_profile_weighting;

	if (pfBlendTime)
	{
		// Accumulate smoothing time up to specified max.
		if (*pfBlendTime > m_profile_smooth_time)
			*pfBlendTime = m_profile_smooth_time;
		else
		{
			// Return smoothing for custom blend time.
			if (*pfBlendTime == 0.f)
				return 1.f;
			if (m_profile_weighting == 1)
				// Weight all frames evenly.
				return 1.f - expf(-m_fAvgFrameTime / *pfBlendTime);
			else if (m_profile_weighting == 2)
				// Weight according to frame time.
				return 1.f - expf(-min(m_fRealFrameTime, fMAX_BLEND_FRAME_TIME) / *pfBlendTime);
		}
	}
	return m_fProfileBlend;
}

/////////////////////////////////////////////////////
void CTimer::RefreshGameTime(int64 curTime)
{
	assert(curTime + m_lOffsetTime >= 0);
	m_CurrTime[ETIMER_GAME].SetSeconds(TicksToSeconds(curTime + m_lOffsetTime));
}

/////////////////////////////////////////////////////
void CTimer::RefreshUITime(int64 curTime)
{
	assert(curTime >= 0);
	m_CurrTime[ETIMER_UI].SetSeconds(TicksToSeconds(curTime));
}


/////////////////////////////////////////////////////
void CTimer::UpdateOnFrameStart()
{
	if (!m_bEnabled)
		return;

	m_nFrameCounter++;

#ifdef PROFILING
	m_fFrameTime = 0.020f; // 20ms = 50fps
	g_lCurrentTime += (int)(m_fFrameTime*(float)(CTimeValue::TIMEVALUE_PRECISION));
	m_lCurrentTime = g_lCurrentTime;
	m_lLastTime = m_lCurrentTime;
	RefreshGameTime(m_lCurrentTime);
	RefreshUITime(m_lCurrentTime);
	return;
#endif
 
	int64 now = CryGetTicks();

	if (m_lForcedGameTime >= 0)
	{
		// m_lCurrentTime contains the time, which should be current
		// but time has passed since Serialize until UpdateOnFrameStart
		// so we have to make sure to compensate!
		m_lOffsetTime = m_lForcedGameTime - now + m_lBaseTime;
		if (m_TimeDebug)
			CryLogAlways("[CTimer] PostSerialization: Frame=%d Cur=%lld Now=%lld Off=%lld Async=%f CurrTime=%f UI=%f", gEnv->pRenderer->GetFrameID(false), (long long)m_lCurrentTime, (long long)now, (long long)m_lOffsetTime, GetAsyncCurTime(), GetCurrTime(ETIMER_GAME), GetCurrTime(ETIMER_UI));
		m_lLastTime = now - m_lBaseTime;
		m_lForcedGameTime = -1;
	}

	// Real time. 
	m_lCurrentTime = now - m_lBaseTime;

	m_fRealFrameTime = m_fFrameTime = TicksToSeconds(m_lCurrentTime - m_lLastTime);

	if( 0 != m_fixed_time_step) // Absolute zero used as a switch. looks wrong, but runs right ;-)
	{
		if (m_fixed_time_step < 0)
		{
			// Enforce real framerate by sleeping.
			float sleep = -m_fixed_time_step - m_fFrameTime;
			if (sleep > 0)
			{
				CrySleep((unsigned int)(sleep*1000.f));
				m_lCurrentTime = CryGetTicks() - m_lBaseTime;
				m_fRealFrameTime = TicksToSeconds(m_lCurrentTime - m_lLastTime);
			}
		}
		m_fFrameTime = abs(m_fixed_time_step);
	}
	else
	{
		// Clamp it
		m_fFrameTime = min(m_fFrameTime, m_max_time_step);

		// Dilate it.
		m_fFrameTime *= m_time_scale;
	}

	if (m_fFrameTime < 0) 
		m_fFrameTime = 0;



//-----------------------------------------------

	if (m_TimeSmoothing>0)
	{
		m_fAverageFrameTime = GetAverageFrameTime( 0.25f, m_fFrameTime, m_fAverageFrameTime); 
		m_fFrameTime=m_fAverageFrameTime;
	}

	//else

	{	
		// Adjust.
		if (m_fFrameTime != m_fRealFrameTime)
		{
			float fBefore = GetAsyncCurTime();
			int64 nAdjust = (int64)((m_fFrameTime - m_fRealFrameTime) * (double)(m_lTicksPerSec));
			m_lCurrentTime += nAdjust;
			m_lBaseTime -= nAdjust;
			if (m_TimeDebug)
				CryLogAlways("[CTimer]: Frame=%d Adj: FrameTime=%f Real=%f (delta=%.8f) -> AsyncCurTime=%f (was %f)", gEnv->pRenderer->GetFrameID(false), m_fFrameTime, m_fRealFrameTime, (m_fFrameTime - m_fRealFrameTime), GetAsyncCurTime(), fBefore);
		}
	}

	RefreshUITime(m_lCurrentTime);
	if (m_bGameTimerPaused == false)
		RefreshGameTime(m_lCurrentTime);

	m_lLastTime = m_lCurrentTime;

	if (m_fRealFrameTime < 0.f)
		// Can happen after loading a saved game and 
		// at any time on dual core AMD machines and laptops :)
		m_fRealFrameTime = 0.0f;

	UpdateBlending();

	/* Check for AsyncCurTime. Should never jump back! */
#if defined(USER_alexl)
	static float sAsync = GetAsyncCurTime();
	static int64 sOldBaseTime = m_lBaseTime;
	const float async = GetAsyncCurTime();
	if (async < sAsync)
	{
		assert (false);
		CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"[CTimer] Frame=%d AsyncCurTime: Time Jump Detected! LastTime=%f CurTime=%f", gEnv->pRenderer->GetFrameID(false), sAsync, async );
		CryWarning( VALIDATOR_MODULE_SYSTEM,VALIDATOR_WARNING,"[CTimer]   OldBase=%lld  NewBase=%lld", (long long)sOldBaseTime, (long long)m_lBaseTime);
	}
	sAsync = async;
#endif

	if (m_TimeDebug)
		CryLogAlways("[CTimer]: Frame=%d Cur=%lld Now=%lld Off=%lld Async=%f CurrTime=%f UI=%f", gEnv->pRenderer->GetFrameID(false), (long long)m_lCurrentTime, (long long)now, (long long)m_lOffsetTime, GetAsyncCurTime(), GetCurrTime(ETIMER_GAME), GetCurrTime(ETIMER_UI));
}

//------------------------------------------------------------------------
//--  average frame-times to avoid stalls and peaks in framerate
//--    note that is is time-base averaging and not frame-based
//------------------------------------------------------------------------
f32 CTimer::GetAverageFrameTime(f32 sec, f32 FrameTime, f32 LastAverageFrameTime) 
{ 
	uint32 numFT=	MAX_FRAME_AVERAGE;
	for (int32 i=(numFT-2); i>-1; i--)
		m_arrFrameTimes[i+1] = m_arrFrameTimes[i];

	if (FrameTime>0.4f) FrameTime = 0.4f;
	if (FrameTime<0.0f) FrameTime = 0.0f;
	m_arrFrameTimes[0] = FrameTime;

	//get smoothed frame
	uint32 avrg_ftime = 1;
	if (LastAverageFrameTime)
	{
		avrg_ftime = uint32(sec/LastAverageFrameTime+0.5f); //average the frame-times for a certain time-period (sec)
		if (avrg_ftime>numFT)	avrg_ftime=numFT;
		if (avrg_ftime<1)	avrg_ftime=1;
	}

	f32 AverageFrameTime=0;
	for (uint32 i=0; i<avrg_ftime; i++)	
		AverageFrameTime += m_arrFrameTimes[i];
	AverageFrameTime /= avrg_ftime;

	//don't smooth if we pause the game
	if (FrameTime<0.0001f)
		AverageFrameTime=FrameTime;
	
//	g_YLine+=66.0f;
//	float fColor[4] = {1,0,1,1};
//	g_pIRenderer->Draw2dLabel( 1,g_YLine, 1.3f, fColor, false,"AverageFrameTime: keys:%d   time:%f %f", avrg_ftime ,AverageFrameTime,sec/LastAverageFrameTime);	
//	g_YLine+=16.0f;

	return AverageFrameTime;
}


/////////////////////////////////////////////////////
void CTimer::ResetTimer()
{
	m_lBaseTime = CryGetTicks();
	m_lLastTime = m_lCurrentTime = 0;
	m_fFrameTime = 0;
	RefreshGameTime(m_lCurrentTime);
	RefreshUITime(m_lCurrentTime);
	m_lForcedGameTime = -1;
	m_bGameTimerPaused = false;
	m_lGameTimerPausedTime = 0;
}

/////////////////////////////////////////////////////
void CTimer::EnableTimer( const bool bEnable ) 
{ 
	m_bEnabled = bEnable; 
}

bool CTimer::IsTimerEnabled() const
{
	return m_bEnabled;
}

/////////////////////////////////////////////////////
CTimeValue CTimer::GetAsyncTime() const
{
	int64 llNow = CryGetTicks();
	double fConvert = CTimeValue::TIMEVALUE_PRECISION * m_fSecsPerTick;
	return CTimeValue(int64(llNow*fConvert));
}




void CTimer::Serialize( TSerialize ser )
{
	// cannot change m_lBaseTime, as this is used for async time (which shouldn't be affected by save games)
	if (ser.IsWriting())
	{
		// why we have to do this: see comment in PauseTimer
		// (m_lCurrentTime+m_lOffsetTime) might not reflect the current game time
		// if there had been no UpdateOnFrameStart yet
		int64 currentGameTime = 0;
		if (m_lForcedGameTime >= 0)
			currentGameTime = m_lForcedGameTime;
		else
			currentGameTime = m_lCurrentTime + m_lOffsetTime;

		ser.Value("curTime", currentGameTime);
		ser.Value("ticksPerSecond", m_lTicksPerSec);
	}
	else
	{
		int64 ticksPerSecond = 1, curTime = 1;
		ser.Value("curTime", curTime);
		ser.Value("ticksPerSecond", ticksPerSecond);
		curTime = (int64)((long double)curTime * m_lTicksPerSec / ticksPerSecond);
		m_lOffsetTime = 0; // will be corrected in UpdateOnFrameStart
		m_lForcedGameTime = curTime;
		if(m_lGameTimerPausedTime && m_bGameTimerPaused)
			m_lGameTimerPausedTime = curTime;
		RefreshGameTime(m_lForcedGameTime);
		if (m_TimeDebug)
		{
			int64 now = CryGetTicks();
			CryLogAlways("[CTimer]: Serialize: Frame=%d Cur=%lld Now=%lld Off=%lld Async=%f CurrTime=%f UI=%f", gEnv->pRenderer->GetFrameID(false), (long long)m_lCurrentTime, (long long)now, (long long)m_lOffsetTime, GetAsyncCurTime(), GetCurrTime(ETIMER_GAME), GetCurrTime(ETIMER_UI));
		}
	}
}

//! try to pause/unpause a timer
//  returns true if successfully paused/unpaused, false otherwise
bool CTimer::PauseTimer(ETimer which, bool bPause)
{
	if (which != ETIMER_GAME)
		return false;

	if (m_bGameTimerPaused == bPause)
		return false;

	if (bPause)
	{
		// if we have not yet had an UpdateOnFrameStart call
		// this can happen
		// Frame I: Pause On
		// Frame I+2: Pause Off
		// Frame I+2: Pause On  (no UpdateOnFrameStart, so m_lForcedGameTime contains the to-be-set time,
		//                       and m_lCurrentTime+m_lOffsetTime is wrong, because m_lOffsetTime is 0
		//                       as m_lOffsetTime was set to 0 during unpausing and was not yet corrected
		//                       in UpdateOnFrameStart.
		//                       Maybe we should call UpdateOnFrameStart internally or at least when we Unpause
		//                       int64 now = CryGetTicks();
		//                       m_lOffsetTime = m_lForcedGameTime - now + m_lBaseTime;
		if (m_lForcedGameTime >= 0)
			m_lGameTimerPausedTime = m_lForcedGameTime;
		else
			m_lGameTimerPausedTime = m_lCurrentTime + m_lOffsetTime;
		if (m_TimeDebug)
			CryLogAlways("[CTimer]: Pausing ON: Frame=%d Cur=%lld Off=%lld Async=%f CurrTime=%f UI=%f", gEnv->pRenderer->GetFrameID(false), (long long)m_lCurrentTime, (long long)m_lOffsetTime, GetAsyncCurTime(), GetCurrTime(ETIMER_GAME), GetCurrTime(ETIMER_UI));
	}
	else
	{
		if (m_lGameTimerPausedTime > 0)
		{
			m_lOffsetTime = 0;
			RefreshGameTime(m_lGameTimerPausedTime);
			m_lForcedGameTime = m_lGameTimerPausedTime;
			if (m_TimeDebug)
				CryLogAlways("[CTimer]: Pausing OFF: Frame=%d Cur=%lld Off=%lld Async=%f CurrTime=%f UI=%f", gEnv->pRenderer->GetFrameID(false), (long long)m_lCurrentTime, (long long)m_lOffsetTime, GetAsyncCurTime(), GetCurrTime(ETIMER_GAME), GetCurrTime(ETIMER_UI));
		}
		m_lGameTimerPausedTime = 0;
	}
	m_bGameTimerPaused = bPause;
	return true;
}

//! determine if a timer is paused
//  returns true if paused, false otherwise
bool CTimer::IsTimerPaused(ETimer which)
{
	if (which != ETIMER_GAME)
		return false;
	return m_bGameTimerPaused;
}

//! try to set a timer
//  return true if successful, false otherwise
bool CTimer::SetTimer(ETimer which, float timeInSeconds)
{
	if (which != ETIMER_GAME)
		return false;
	m_lOffsetTime = 0;
	m_lForcedGameTime = (int64) ((double)(timeInSeconds)*(double)(m_lTicksPerSec));
	RefreshGameTime(m_lForcedGameTime);
	return true;
}

ITimer* CTimer::CreateNewTimer()
{
	return new CTimer();
}

void CTimer::SecondsToDateUTC(time_t inTime, struct tm& outDateUTC)
{
#if !defined(__SPU__)
	outDateUTC = *gmtime(&inTime);
#endif 
}

#if defined (WIN32) || defined(WIN64) || defined(XENON)
time_t gmt_to_local_win32(void)
{
	TIME_ZONE_INFORMATION tzinfo;
	DWORD dwStandardDaylight;
	long bias;

	dwStandardDaylight = GetTimeZoneInformation(&tzinfo);
	bias = tzinfo.Bias;

	if (dwStandardDaylight == TIME_ZONE_ID_STANDARD)
		bias += tzinfo.StandardBias;

	if (dwStandardDaylight == TIME_ZONE_ID_DAYLIGHT)
		bias += tzinfo.DaylightBias;

	return (- bias * 60);
}
#endif

#if !defined(__SPU__)





time_t CTimer::DateToSecondsUTC(struct tm& inDate)
{
#if defined (WIN32) || defined(XENON)
	return mktime(&inDate) + gmt_to_local_win32();
#elif defined (LINUX)
#if defined (HAVE_TIMEGM)
	// return timegm(&inDate);
#else
	// craig: temp disabled the +tm.tm_gmtoff because i can't see the intention here
	// and it doesn't compile anymore
	// alexl: tm_gmtoff is the offset to greenwhich mean time, whereas mktime uses localtime
	//        but not all linux distributions have it...
	return mktime(&inDate) /*+ tm.tm_gmtoff*/;
#endif








#else
	return mktime(&inDate);
#endif
}

#endif